package com.note.middleware.service.impl;

import com.note.common.domain.FileRecord;
import com.note.middleware.dto.BucketPolicyConfigDto;
import com.note.middleware.dto.MinioUploadDto;
import com.note.common.utils.FileUtils;
import com.note.common.vo.ResponseVo;
import com.note.middleware.mapper.FileRecordMapper;
import com.note.middleware.service.FileService;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 文件服务实现类
 */
@Slf4j
@Service
public class FileServiceImpl implements FileService {

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.bucketName}")
    private String bucketName;

    @Value("${minio.accessKey}")
    private String accessKey;

    @Value("${minio.secretKey}")
    private String secretKey;

    @Value("${minio.oss-url}")
    private String ossUrl;

    @Value("${minio.file.extensions}")
    private String[] fileExtensions;

    @Autowired
    private FileRecordMapper fileRecordMapper;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private MinioClient minioClient;

    /**
     * 上传文件
     *
     * @param userId 用户ID
     * @param file 文件
     * @param businessId 关联业务ID（如笔记ID等）
     * @return 上传结果
     */
    @Override
    public ResponseVo<MinioUploadDto> uploadFile(Long userId, MultipartFile file, String businessId) {
        try {
            // 1. 验证文件大小
            long size = file.getSize();
            if (size == 0L) {
                return ResponseVo.error("禁止上传空文件");
            }
            
            // 10MB文件大小限制
            if (size > 10 * 1024 * 1024) {
                return ResponseVo.error("文件大小不能超过10MB");
            }

            // 2. 验证文件后缀
            String originalFilename = file.getOriginalFilename();
            if (!StringUtils.hasText(originalFilename)) {
                return ResponseVo.error("文件名不能为空");
            }

            int index = originalFilename.lastIndexOf(".");
            if (index == -1) {
                return ResponseVo.error("禁止上传无后缀文件");
            }

            String extension = originalFilename.substring(index + 1).toLowerCase(Locale.ROOT);
            if (!Arrays.asList(fileExtensions).contains(extension)) {
                return ResponseVo.error("不支持的文件类型，仅支持: " + String.join(",", fileExtensions));
            }

            // 3. 生成存储对象名称
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
            String datePath = dateFormat.format(new Date());
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            String objectName = String.format("%s/%s-%d.%s", datePath, uuid, userId, extension);

            // 5. 检查并创建存储桶
            boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!bucketExists) {
                // 创建桶并设置只读权限
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                setBucketPolicy(minioClient, bucketName);
            }

            // 6. 上传文件
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .contentType(file.getContentType())
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .build());

            // 7. 构建返回结果
            String url = String.format("%s/%s/%s", ossUrl, bucketName, objectName);
            MinioUploadDto uploadDto = new MinioUploadDto();
            uploadDto.setName(objectName);
            uploadDto.setUrl(url);
            
            // 8. 计算文件MD5
            File tempFile = FileUtils.transferToFile(file);
            String md5 = FileUtils.getFileMD5(tempFile);
            uploadDto.setFileMd5(md5);

            // 9. 异步保存文件记录
            saveFileRecordAsync(userId, file, objectName, url, md5, businessId);

            return ResponseVo.success("上传成功", uploadDto);
        } catch (Exception e) {
            log.error("文件上传失败", e);
            return ResponseVo.error("文件上传失败");
        }
    }

    /**
     * 保存文件记录
     *
     * @param fileRecord 文件记录
     * @return 保存结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveFileRecord(FileRecord fileRecord) {
        return fileRecordMapper.insert(fileRecord) > 0;
    }

    /**
     * 异步保存文件记录
     */
    @Async
    public void saveFileRecordAsync(Long userId, MultipartFile file, String objectName, 
                                   String url, String md5, String businessId) {
        try {
            FileRecord fileRecord = new FileRecord();
            fileRecord.setUserId(userId);
            fileRecord.setFileName(objectName);
            fileRecord.setOriginalFileName(file.getOriginalFilename());
            fileRecord.setUrl(url);
            fileRecord.setFileSize(file.getSize());
            fileRecord.setContentType(file.getContentType());
            fileRecord.setFileMd5(md5);
            fileRecord.setBucketName(bucketName);
            fileRecord.setObjectName(objectName);
            fileRecord.setBusinessId(businessId);
            fileRecord.setStatus(1);
            
            // 保存到数据库
            saveFileRecord(fileRecord);
            log.info("文件记录异步保存成功: {}", objectName);
        } catch (Exception e) {
            log.error("文件记录保存失败: {}", e.getMessage(), e);
        }
    }

    /**
     * 设置存储桶策略为公共读
     */
    private void setBucketPolicy(MinioClient minioClient, String bucketName) {
        try {
            // 创建存储桶策略 - 设置为只读
            BucketPolicyConfigDto.Statement statement = BucketPolicyConfigDto.Statement.builder()
                    .Effect("Allow")
                    .Principal("*")
                    .Action("s3:GetObject")
                    .Resource("arn:aws:s3:::" + bucketName + "/*")
                    .build();

            BucketPolicyConfigDto policyConfigDto = BucketPolicyConfigDto.builder()
                    .version("2012-10-17")
                    .statements(Collections.singletonList(statement))
                    .build();

            // 将策略转换为JSON并应用
            String policy = String.format(
                    "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::%s/*\"]}]}",
                    bucketName);
            
            minioClient.setBucketPolicy(
                    SetBucketPolicyArgs.builder()
                            .bucket(bucketName)
                            .config(policy)
                            .build());
                            
            log.info("存储桶策略设置成功: {}", bucketName);
        } catch (Exception e) {
            log.error("设置存储桶策略失败: {}", e.getMessage(), e);
        }
    }
} 